import moment from "dayjs";
import Duration from "dayjs/plugin/duration";
import { JSEncrypt } from "jsencrypt";
import { b64tohex } from "jsencrypt/lib/lib/jsbn/base64";
import base62 from "base62";
import AsyncLock from "async-lock";
import { clipboard } from "electron";
import { ApiCode, isEdu, isTrialVersion } from "@/mdesign-lib/mdesign-app.umd.js";
// export * from "./Storage";
import lodash from "lodash";

moment.extend(Duration);

/**
 * 获取唯一的id，以时间为基准，
 */
export const getUid = (() => {
  let now = "";
  let random = "";
  let index = 0;
  let p = "";
  const reset = () => {
    now = base62.encode(Date.now() - 1634869060000);
    random = base62.encode(parseInt(lodash.random(0, 1, true) * 10000 + ""));
    index = 0;
    p = `${now}-${random}-`;
  };
  reset();
  return () => {
    if (index >= Number.MAX_SAFE_INTEGER) {
      reset();
    }
    index++;
    return `${p}${base62.encode(index)}`;
  };
})();
// 协同功能弹窗
export const validRemoteIsLogin = () => {
  if (!(app.global.user && app.isAppActive)) {
    app.$confirm.popup({
      title: "提示",
      subTitle: "请先登录协同",
      status: "error",
      cancelText: ""
    });
    return false;
  } else {
    return true;
  }
};

// 未登录或未激活license时弹窗
export const validLicenseOrRemote = () => {
  let tipMsg = "请登录协同账户或激活License解锁更多功能";
  if (isEdu) {
    tipMsg = "请登录协同账户解锁更多功能";
  } else if (isTrialVersion) {
    tipMsg = "请激活License解锁更多功能";
  }

  app.$confirm.popup({
    title: "提示",
    subTitle: "功能未解锁",
    content: tipMsg,
    type: "warning",
    cancelText: "",
    keepAlive: true
  });
};

/**
 * 根据点号分割的字符串或字符串数组从一个对象中取值
 * @param {Object} obj {a:[{b:'xx'}]}
 * @param {Array} prop a.0.b or ['a','0','b']
 */
export const getObjectValue = (obj: any, prop: string | string[]) => {
  let proArr;
  if (typeof prop === "string") {
    proArr = prop.split(".");
  } else if (Array.isArray(prop)) {
    proArr = prop;
  } else {
    // console.error('getObjectValue  prop must be string or array');
    return undefined;
  }
  proArr = proArr.filter(i => i !== "");
  let value = obj;
  for (let i = 0; i < proArr.length; i++) {
    const p = proArr[i];
    value = value[p];
    if (value === undefined) {
      return undefined;
    }
    if (value === null) {
      if (i === proArr.length - 1) {
        return null;
      } else {
        return undefined;
      }
    }
  }
  return value;
};
/**
 * 设置formData数据
 * @param {Object} to
 * @param {Object} from
 */
export const setData = (to: any, from: any) => {
  for (let key in to) {
    if (from[key] !== undefined) {
      to[key] = from[key];
    }
  }
};

export class TreeNode {
  // label?:string|number
  // value?:string|number
  children?: TreeNode[];
  // disabled?:boolean

  [prop: string]: any;
}
/**
 * 遍历树结构，fn一旦返回false就会停止遍历
 * @param {Array} tree 树结构
 * @param {Function} fn 对每个节点执行一遍fn方法,如果return false就会停止遍历
 * @param {String} childrenKey  作为children的键
 * @param parent
 *
 */
export function treeForEach<T>(
  tree: T[],
  fn: (node: T, parent?: T | undefined, index?: number, level?: number) => boolean | void | "skipChildren" | unknown,
  childrenKey = "children",
  parent?: T,
  level = 1
) {
  let size = tree.length;
  for (let i = 0; i < size; i++) {
    const curNode = tree[i];
    const res = fn(curNode, parent, i, level);
    if (res === false) return false;
    if (res === "skipChildren") continue;
    const children = curNode?.[childrenKey];
    if (children?.length) {
      if (treeForEach(children, fn, childrenKey, curNode, level + 1) === false) {
        return false;
      }
    }
  }
}

/**
 * 树结构遍历
 * @param tree
 * @param fn 遍历到node节点时执行的回调方法
 * @param after node节点以及其所有子节点遍历完成后 回调该方法传入该node节点
 * @param childrenKey
 * @param parent
 * @returns
 */
export function treeForEachAfter<T>(
  tree: T[],
  fn: (node: T, parent?: T | undefined) => boolean | void | "skipChildren",
  after: (node: T, parent?: T | undefined) => void, // 子节点遍历结束后，回调父节点
  childrenKey = "children",
  parent?: T
) {
  let size = tree.length;
  for (let i = 0; i < size; i++) {
    const curNode = tree[i];
    const res = fn(curNode, parent);
    if (res === false) return false;
    if (res === "skipChildren") continue;
    const children = curNode?.[childrenKey];
    if (children?.length) {
      if (treeForEachAfter(children, fn, after, childrenKey, curNode) === false) {
        return false;
      }
      after(curNode, parent);
    }
  }
}

/**
 * 异步遍历树结构
 * @param tree
 * @param fn 异步回调方法，返回Promise
 * @param childrenKey
 * @param parent
 * @returns
 */
export async function treeForEachAsync<T>(
  tree: T[],
  fn: (node: T, parent?: T, index?: number) => Promise<boolean | void | "skipChildren">,
  childrenKey = "children",
  parent?: T
) {
  for (let curNode of tree) {
    // const curNode = tree[i];
    const result = await fn(curNode, parent);
    if (result === false) return false;
    if (result === "skipChildren") return;
    const children = curNode?.[childrenKey];
    if (children?.length) {
      const result2 = await treeForEachAsync(children, fn, childrenKey, curNode);
      if (result2 === false) {
        return false;
      }
    }
  }
}

/**
 * @param {array} node
 * @param {function} callback
 * @returns {void}
 */
// export const searchTree = <T>(node: T | T[], depth = 0, callback: (node: T, depth: number) => T[] | void) => {
//   if (Array.isArray(node)) {
//     node.forEach(child => searchTree(child, depth, callback));
//   } else {
//     const children = callback(node, depth);
//     depth++;
//     if (children) {
//       searchTree(children, depth, callback);
//     }
//   }
// };

/**
 * 从树结构里根据value找到第一个结点
 * @param {*} value
 * @param {Array} tree [{label,value,children}]
 */
export const findNodeByValue = (tree: TreeNode[], value: string | number, key = "value") => {
  let result;
  treeForEach(tree, (node: TreeNode) => {
    if (node[key] === value) {
      result = node;
      return false;
    }
  });
  return result;
};
/**
 * 从树结构里根据label找到第一个结点
 * @param {Array} tree [{label,value,children}]
 * @param label
 */
export const findNodeByLabel = (tree: TreeNode[], label: string | number) => {
  let result;
  treeForEach(tree, (node: TreeNode) => {
    if (node.label === label) {
      result = node;
      return false;
    }
  });
  return result;
};
/**
 *
 * @param {Array} tree [{label,value,children}]
 * @param {Object} option
 * {
 * labelKey String 将转换为label的键  default:'label'
 * valueKey String 将转换为value的键  default:'value'
 * childrenKey String 将转换为children的键 default:'children'
 * clean Boolean 是否剔除其余属性，default:true
 * getValue Function 用于获取value的函数，传入当前节点,优先级高于valueKey default:undefined
 * getLabel Function 用于获取label的函数，传入当前节点,优先级高于labelKey default:undefined
 * getChildren Function 用于获取label的函数，传入当前节点,优先级高于childrenKey default:undefined
 * deep Number 转化树结构的深度,为0时转化所有，默认为0 default:0
 * }
 * @param {Array} result  目标数组，不用传 default:[]
 * @param parent
 *
 * 此方法不会修改原数据，会返回一个新的数组
 * example:
 * initTree(data, { labelKey: 'region_name', valueKey: 'national_code', childrenKey: 'child_action' })
 *
 */
export const initTree = (
  tree: any[],
  option: {
    labelKey?: string;
    valueKey?: string;
    childrenKey?: string;
    clean?: boolean;
    getValue?: Function;
    getLabel?: Function;
    getChildren?: Function;
    deep?: number;
    keepParent?: boolean;
    customFn?: Function;
  },
  result: TreeNode[] = [],
  parent?: TreeNode
) => {
  const {
    labelKey = "label",
    valueKey = "value",
    childrenKey = "children",
    clean = true,
    getValue,
    getLabel,
    getChildren,
    deep = 0,
    keepParent = false,
    customFn = undefined
  } = option;
  for (let i = 0; i < tree.length; i++) {
    result[i] = {
      label: getLabel ? getLabel(tree[i]) : tree[i][labelKey],
      value: getValue ? getValue(tree[i]) : tree[i][valueKey]
    };
    if (!clean) {
      result[i] = { ...tree[i], ...result[i] };
    }
    if (keepParent) {
      result[i].parent = parent;
    }
    if (customFn) {
      customFn(result[i], tree[i], parent);
    }
    const _children = getChildren ? getChildren(tree[i]) : tree[i][childrenKey];
    if (deep !== 1 && _children && _children.length) {
      result[i].children = [];
      initTree(_children, deep > 1 ? { ...option, deep: deep - 1 } : option, result[i].children, result[i]);
    }
  }
  return result;
};
/**
 * 根据value查询所在树中的路径
 * @param {*} tree 树结构
 * @param {*} value value
 * @param keyName
 * @param {*} result 递归存储数据，不用传
 * @param {*} deep 递归存储数据，不用传
 */
export function getNodePath<T extends TreeNode>(
  tree: T[],
  value: string | number,
  keyName = "value",
  result: T[] = [],
  deep = 0
): T[] | undefined {
  for (let i = 0; i < tree.length; i++) {
    result[deep] = tree[i];
    result.length = deep + 1;
    if (tree[i][keyName] === value) return result;
    const children = tree[i].children;

    if (children && children.length) {
      const exist = getNodePath<T>(children as T[], value, keyName, result, deep + 1);
      if (exist) {
        return exist;
      }
    }
  }
}
export function getNodePathByNode<T extends TreeNode>(
  tree: T[],
  matchNode: (node: T) => boolean,
  result: T[] = [],
  deep = 0
): T[] | undefined {
  for (let i = 0; i < tree.length; i++) {
    result[deep] = tree[i];
    result.length = deep + 1;
    if (matchNode(tree[i])) return result;
    const children = tree[i].children;

    if (children && children.length) {
      const exist = getNodePathByNode<T>(children as T[], matchNode, result, deep + 1);
      if (exist) {
        return exist;
      }
    }
  }
}
export const getNodeParent = (tree: TreeNode[], value: string | number) => {
  let result;
  treeForEach(tree, (node: TreeNode) => {
    if (node.children && node.children.find(i => i.value === value)) {
      result = node;
      return false;
    }
  });
  return result;
};

// 深冻结
export const deepFreeze = (obj: any) => {
  const propNames = Object.getOwnPropertyNames(obj);
  propNames.forEach(function (name) {
    const prop = obj[name];
    if (typeof prop == "object" && prop !== null) {
      deepFreeze(prop);
    }
  });
  return Object.freeze(obj);
};

// 等待 单位秒
export const sleep = (s = 0) => new Promise(r => setTimeout(r, s * 1000));

// 预加载图片
export const preloadImgs = (urls: string[]) => {
  urls.forEach(url => {
    const img = new Image();
    img.src = url;
  });
};

/**
 *
 * @param {Number} num   格式化目标
 * @param {Number} precision  精度
 * @param {String} prefix 前缀
 * @param {String} suffix 后缀
 */
export const getFormatNum = (num: number, precision = 2, prefix = "", suffix = "") => {
  let num2 = +num;
  if (isNaN(num2)) {
    num2 = 0;
    console.error("getFormatNum：不是一个数字或数字格式的字符串 " + num);
    return num;
  }
  const arr = num2.toFixed(precision).split(".");
  let length = arr[0].length;
  let str = "";
  let index = length - 1;
  while (index >= 0) {
    str = arr[0].charAt(index) + str;
    if (index !== 0 && index !== length - 1 && (length - index) % 3 === 0) {
      str = "," + str;
    }
    index--;
  }
  if (arr[1]) {
    str += "." + arr[1];
  }
  return prefix + str + suffix;
};

export function pickProp<T>(obj: T, props: (keyof T)[]) {
  const result = {} as Partial<T>;
  props.forEach(prop => {
    result[prop] = obj[prop];
  });
  return result;
}

export const getSiderWidthRange = () => {
  const width = document.body.clientWidth;
  let siderWidth;
  if (width < 1280) {
    siderWidth = 232;
  } else if (width < 1440) {
    siderWidth = 256;
  } else {
    siderWidth = 272;
  }
  return {
    siderWidth,
    minWidth: 232,
    maxWidth: siderWidth * 2,
    bodyWidth: width
  };
};

export const DAY_FORMAT = "YYYY-MM-DD";

export const formatTime = (time: string | number | Date, format = "YYYY-MM-DD HH:mm:ss") => {
  if (!time) return "";
  return moment(time).format(format);
};
const timeDiffConfig = [
  { value: "years", label: "年前" },
  { value: "months", label: "月前" },
  { value: "weeks", label: "周前" },
  { value: "days", label: "天前" },
  { value: "hours", label: "小时前" },
  { value: "minutes", label: "分钟前" },
  { value: "seconds", label: "秒前" }
];
export const getTimeDiff = (date: number | string) => {
  const diff = Date.now() - new Date(date).getTime();
  const duration = moment.duration(diff);

  for (let i = 0; i < timeDiffConfig.length; i++) {
    const temp = timeDiffConfig[i];
    const value = duration[temp.value as "years" | "months"]();
    if (value) {
      return value + temp.label;
    }
  }
  return "刚才";
};
/**
 *
 * @param fn
 * @param delay  等待次数  每次0.1秒
 * @returns
 */
export const sleepUntil = (fn: (t: number) => boolean | void, delay = 10, gap = 0.1) => {
  return new Promise((rs, rj) => {
    let time = 0;
    const timer = setInterval(() => {
      time += gap;
      const result = fn(time);
      if (result) {
        rs(result);
        clearInterval(timer);
      }
      if (time >= delay) {
        rj();
        clearInterval(timer);
      }
    }, gap * 1000);
  });
};

// 转意符换成普通字符
const regex = /&(lt|gt|nbsp|amp|quot);/gi;
export const escape2Html = (str: string) => {
  if (str && typeof str === "string") {
    let arrEntities = { lt: "<", gt: ">", nbsp: " ", amp: "&", quot: '"' } as any;
    return str
      .replace(regex, function (all, t) {
        return arrEntities[t];
      })
      .replace("<br>", "");
  }
};

// const excludeKeys = ['get', 'set', 'delete', 'has', 'forEach'];
// export class VueMap<K> {
//   map:{[prop:string]:K}
//   constructor(){
//     this.map = {};
//   }
//   get(key:string){
//     return this.map[key];
//   }
//   set(key:string, value:any){
//     Vue.set(this.map, key, value);
//   }
//   delete(key:string){
//     Vue.delete(this.map, key);
//   }
//   has(key:string){
//     return this.map.hasOwnProperty(key);
//   }
//   forEach(fn:Function){
//     Object.entries(this.map).forEach(([key, value]) => {
//       fn(value, key);
//     });
//   }

// }

// export const getStyleObj = (str:string):{[p:string]:string} => {

//   const result = {};
//   if (!str) return result;
//   // const arr = str.split(';');
//   // for (let i = 0; i < arr.length; i++) {
//   //   const entry = arr[i];
//   //   const values = entry.split('=');
//   //   const key = values[0];
//   //   let value = values[1];
//   //   const nextEntry = arr[i + 1];
//   //   const reg = /^base64,/;
//   //   if (key === 'image' && nextEntry && reg.test(nextEntry)) { // image是base64格式时
//   //     value += ';' + nextEntry;
//   //     i++;
//   //   }
//   //   result[key] = value === undefined ? true : value;
//   // }
//   str.split(';').filter(Boolean).forEach(entry => {
//     const values = entry.split('=');
//     result[values[0]] = values[1] === undefined ? true : values[1];
//   });
//   return result;

// };
// export const getStyleStr = (obj:object):string => {
//   let str = '';
//   Object.keys(obj).forEach(key => {
//     if (obj[key] === undefined) return;
//     str += key;
//     if (obj[key] === true){
//       str += ';';
//     } else {
//       str += ('=' + obj[key] + ';');
//     }

//   });
//   return str;

// };

export const getImgPath = (typeName: string): string => {
  return "statics/images/sysml/" + typeName + ".svg";
};

export const uniqueArr = (arr: any[]) => {
  const newArr: any[] = [];
  const map = new Map();
  arr.forEach(i => {
    if (!map.get(i)) {
      map.set(i, true);
      newArr.push(i);
    }
  });
  return newArr;
};

const reg = /\s/g;
/**
 * 去除所有空格
 *
 * @param s
 * @returns {string|*}
 */
export function trimSpace(s: string): string {
  if (s) {
    return s.replace(reg, "");
  } else {
    return "";
  }
}

/**
 * 获取与nameList中的名字都不相同的名字
 * @param {*} nameList 需要比对的名称数组
 * @param {*} initName 名称的初始值
 * @param {*} number 递归的起始值，用于名称的后缀
 */
export const getUniqueName = (nameList: string[], initName: string, number = 0): string => {
  const title = number === 0 ? initName : `${initName}${number}`;
  if (!nameList.includes(title)) {
    return title;
  } else {
    return getUniqueName(nameList, initName, number + 1);
  }
};
export const isEmpty = (obj: any) => {
  return obj === undefined || obj === null;
};
export const isEmptyArr = (arr: Array<any> | undefined | null) => {
  return !arr || arr.length === 0;
};
export const emptyObject = Object.create(null);
Object.freeze(emptyObject);
/**
 * 约束数字范围
 * @param num 约束的数字
 * @param min 最小值
 * @param max 最大值
 */
export const limitNum = (num: number, min: number, max: number) => {
  return Math.min(Math.max(min, num), max);
};
export const uint8ArrayToString = (array: Uint8Array) => {
  let out, i, len, c;
  let char2, char3;

  out = "";
  len = array.length;
  i = 0;
  while (i < len) {
    c = array[i++];
    switch (c >> 4) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12:
      case 13:
        // 110x xxxx   10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
        break;
      case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0));
        break;
    }
  }

  return out;
};
export const parseToBase64 = (buffer: ArrayBuffer, fileName: string): string => {
  let array = new Uint8Array(buffer);
  // let z1 = String.fromCharCode(...z);
  let res = "";
  let chunk = 8 * 1024;
  let i;
  for (i = 0; i < array.length / chunk; i++) {
    res += String.fromCharCode.apply(null, array.slice(i * chunk, (i + 1) * chunk) as any);
  }
  res += String.fromCharCode.apply(null, array.slice(i * chunk) as any);

  let base64String = btoa(res);
  const index = fileName.lastIndexOf(".");
  let suffix = fileName.substring(index + 1);

  if (suffix === "svg") {
    suffix += "+xml";
  }
  base64String = "data:image/" + suffix + ";base64," + base64String;
  return base64String;
};
/**
 * 通用web端选择文件方法
 * @param accept
 * @returns
 */
export const chooseFile = (accept: string) => {
  return new Promise<any>(rs => {
    let inputNode: HTMLInputElement | null = document.getElementById("chooseFileInput") as HTMLInputElement;
    if (inputNode === null) {
      inputNode = document.createElement("input");
      inputNode.setAttribute("id", "chooseFileInput");
      inputNode.setAttribute("style", "display:none");
      document.body.appendChild(inputNode);
    }
    inputNode.setAttribute("type", "file");
    inputNode.setAttribute("accept", accept);

    const changeHandler = (event: any) => {
      const file = event.target.files[0];

      const reader = new FileReader();
      reader.onload = function () {
        rs({ data: reader.result, name: file.name });
      };
      reader.readAsArrayBuffer(file);

      (inputNode as HTMLInputElement).onchange = null;
      (inputNode as any).value = "";
    };
    inputNode.onchange = changeHandler;

    inputNode.click();
  });
};

let textDom: HTMLDivElement;
/**
 * 获取字符串的大小
 * @param text
 * @param option  { width = '', fontSize = 12, fontFamily = '', lineHeight = 1.2, whiteSpace = 'normal', html = false }
 * @returns
 */
export const getTextSize = (
  text: string,
  option: {
    width?: number;
    fontSize?: number;
    fontFamily?: string;
    lineHeight?: number;
    whiteSpace?: string;
    html?: boolean;
    fontWeight?: number;
  } = {}
) => {
  const { width = "", fontSize = 12, fontFamily = "", lineHeight = 1.2, whiteSpace = "normal", html = false, fontWeight = 400 } = option;
  if (!textDom) {
    textDom = document.createElement("div");
    textDom.style.position = "absolute";
    textDom.style.top = "0";
    textDom.style.left = "0";
    textDom.style.zIndex = "-1000";
    textDom.style.opacity = "0";
    textDom.style.pointerEvents = "none";
    textDom.style.display = "inline-block";
    textDom.style.visibility = "hidden";

    document.body.appendChild(textDom);
  }
  textDom.style.width = width === "" ? width : width - 2 + "px";
  textDom.style.fontSize = fontSize + "px";
  textDom.style.fontFamily = fontFamily;
  textDom.style.lineHeight = lineHeight as any as string;
  textDom.style.whiteSpace = whiteSpace === "nowrap" ? "nowrap" : "normal";
  textDom.style.wordBreak = whiteSpace === "nowrap" ? "normal" : "break-all";
  textDom.style.fontWeight = fontWeight as any as string;
  if (html) {
    textDom.innerHTML = text;
  } else {
    textDom.innerText = text;
  }
  //
  return { width: textDom.clientWidth, height: textDom.clientHeight };
};

// setTimeout(() => {
//   let obj = {} as any;
//   for (let i = 32; i < 127; i++) {
//     const str = String.fromCharCode(i).repeat(10000);
//     const size = getTextSize(str, { whiteSpace: 'pre' });
//     obj[String.fromCharCode(i)] = Math.ceil(size.width / 10);
//   }
//   console.log(JSON.stringify(obj));
// }, 1000);

/**
 * rsa分段解密
 * @param content 内容
 */
export const decryptLong = (content: string, privateKey: string) => {
  const enc = new JSEncrypt({});
  enc.setPrivateKey(privateKey);
  let k = enc.getKey();
  let maxLength = 128;
  try {
    let hexStr = b64tohex(content);
    if (hexStr.length > maxLength * 2) {
      let hexStrArr = hexStr.match(/.{1,256}/g) as string[]; // 128位解密。取256位
      const ct = hexStrArr.map(entry => k.decrypt(entry)).join("");

      return ct;
    } else {
      return k.decrypt(hexStr);
    }
  } catch (ex) {
    return "";
  }
};

export interface IBounds {
  x: number;
  y: number;
  width: number;
  height: number;
  absX: number;
  absY: number;
}
export class Bounds implements IBounds {
  constructor(public x = 0, public y = 0, public width = 0, public height = 0, public absX = 0, public absY = 0) {}
}

export class Rect {
  constructor(public x = 0, public y = 0, public width = 0, public height = 0) {}
}

export class Point {
  constructor(public x = 0, public y = 0) {}
}

/**
 * 计算树的最大深度
 * @param arr
 * @param deep 用于递归不用传
 * @param max 用于递归不用传
 * @returns
 */
export const getMenuDepth = (arr: TreeNode[], deep = 1, max = { maxDeep: 1 }) => {
  if (arr.length === 0) return 0;
  arr.forEach(it => {
    if (it.children?.length) {
      deep++;
      max.maxDeep = Math.max(deep, max.maxDeep);
      getMenuDepth(it.children, deep, max);
    }
  });
  return max.maxDeep;
};

export class ResData<T> {
  code = 1000;
  message = "";
  data?: T;
  title = "";
  constructor(code: ApiCode, data?: T, message?: string) {
    this.data = data;
    this.code = code || ApiCode.SUCCESS;
    this.message = message || "success";
  }
}

export enum ActionStatus {
  /**
   * 操作成功
   */
  SUCCESS = "success",

  ERROR = "error"
}

/**
 * 客户端向服务的发送信息的统一格式
 */
export class SocketClientData<T> extends ResData<T> {
  sessionId: string;
  type: string;
  constructor(options: { sessionId: string; type: string; code: ApiCode } & Partial<ResData<T>>) {
    const { data, code, message } = options;
    super(code, data, message);
    const { sessionId, type } = options;
    this.sessionId = sessionId;
    this.type = type;
  }
}

export const arrayEqual = (arr1: any[], arr2: any[]): boolean => {
  if (arr1.length !== arr2.length) return false;
  for (let i = 0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i]) {
      return false;
    }
  }
  return true;
};

/**
 * 判断 基本类型数据组成的数组 是否相等（不考虑顺序 和 重复）
 * @param arr1
 * @param arr2
 * @returns
 */
export const arrayEqualElement = (arr1: any[], arr2: any[]): boolean => {
  if (arr1.length !== arr2.length) return false;
  for (let i = 0; i < arr1.length; i++) {
    if (!arr1.includes(arr2[i]) || !arr2.includes(arr1[i])) {
      return false;
    }
  }
  return true;
};

/**
 * 复制文本
 * @param text
 * @returns
 */
export const copyText = (text: string): boolean => {
  let inputDom: HTMLInputElement = document.createElement("input");
  try {
    inputDom.style.position = "absolute";
    inputDom.style.top = "0";
    inputDom.style.left = "0";
    inputDom.style.zIndex = "-1000";
    inputDom.style.opacity = "0";
    inputDom.style.pointerEvents = "none";
    inputDom.value = text;

    document.body.appendChild(inputDom);
    inputDom.select();
    const success = document.execCommand("copy");
    return success;
  } finally {
    inputDom.remove();
  }
};

/**
 * 文件大小的展示文字
 * @param size 文件比特大小
 */
const kbSize = 1024;
const mbSize = kbSize ** 2;
const gbSize = kbSize ** 3;
export function getFileSizeStr(size: number, fixed = 1) {
  if (!size && size !== 0) return "未知大小";
  if (size < kbSize) {
    return size + "B";
  } else if (size < mbSize) {
    return (size / kbSize).toFixed(fixed) + "KB";
  } else if (size < gbSize) {
    return (size / mbSize).toFixed(fixed) + "MB";
  } else {
    return (size / gbSize).toFixed(fixed) + "GB";
  }
}

/**
 * 获取图片宽高
 * @param data
 */
export const getImageSize = async (data: string): Promise<{ width: number; height: number }> => {
  return new Promise(res => {
    const imageDom = new Image();
    imageDom.src = data;
    imageDom.onload = function () {
      const width = imageDom.width;
      const height = imageDom.height;
      res({ width, height });
    };
  });
};

export const convertImgToBase64 = (path: string): Promise<string> => {
  return new Promise(res => {
    const image = new Image();
    image.src = path;
    image.onload = function () {
      const canvas = document.createElement("canvas");
      canvas.width = image.width;
      canvas.height = image.height;
      let ctx = canvas.getContext("2d");
      if (!ctx) return;
      ctx.drawImage(image, 0, 0, image.width, image.height);
      const url = canvas.toDataURL();
      res(url);
    };
    image.onerror = function () {
      res("data:image/png;");
    };
  });
};

export function toMap<K = string, V = any>(items: Array<V> | Set<V>, idKey = "id"): Map<K, V> {
  const map = new Map<K, V>();
  for (let it of items) {
    map.set(it[idKey], it);
  }
  return map;
}

/**
 * 读取系统剪切板，并按行、列返回数据
 */
export function getClipboardText(isTable = false): string[][] {
  const text = clipboard.readText();
  const result = text.split(/\r\n/).map(row => row.split(/\t/));
  // 如果以\r\n为结尾，需要把最后拆分的元素弹出
  if (text.match(/\r\n$/)) result.pop();
  if (!isTable) result[0].push(...result.splice(1).flat());
  else {
    const len = result[0].length;
    if (result.some(row => row.length != len)) {
      app.$notify({
        type: "warning",
        title: "失败",
        message: "无法识别粘贴板中的内容。请在通用文件中复制数据（例如，HTML，Excel，Word）"
      });
      throw new Error("剪切板内容无效");
    }
  }
  // console.log("run here", result);
  return result;
}

const windowFileNameReg = /[\\/:*?"<>|\n]/;
/**
 * 检查文件名称是否合法
 * @param fileName
 * @returns
 */
export function checkFileNameFormat(fileName: string, fileNamePlaceholder?: string) {
  const result = {
    success: true,
    msg: ""
  };
  if (windowFileNameReg.test(fileName)) {
    result.success = false;
    result.msg = `${fileNamePlaceholder || "文件名称"}格式错误，不得包含 \\ / : * ? " < > | 以及换行符`;
  }

  return result;
}

/**
 * 计算两个字符串数组之间的区别
 * @param strsOld
 * @param strsNew
 * @returns  {
    addStrs, 新增的
    deleteStrs, 删除的
    sameStrs 共有的
  }
 */
export function getDiffOfStrs(strsOld: string[], strsNew: string[]) {
  const map: Record<string, number> = {};
  // 二进制取或， 第一位表示新的有，第二位表示旧的有,只有第一位表示是新增的，只有第二位表示是删除的,两位都有的表示都存在
  strsOld.forEach(str => {
    map[str] = 0b01; //
  });
  strsNew.forEach(str => {
    map[str] = (map[str] || 0b00) | 0b10;
  });
  const addStrs: string[] = [];
  const deleteStrs: string[] = [];
  const sameStrs: string[] = [];

  Object.keys(map).forEach(key => {
    if (map[key] === 0b10) {
      addStrs.push(key);
    } else if (map[key] === 0b01) {
      deleteStrs.push(key);
    } else {
      sameStrs.push(key);
    }
  });
  return {
    addStrs,
    deleteStrs,
    sameStrs
  };
}

const scrollIntoViewLock = new AsyncLock();
export function scrollIntoView(dom: HTMLElement, option: ScrollIntoViewOptions | boolean) {
  scrollIntoViewLock.acquire("scrollIntoViewLockKey", async () => {
    dom.scrollIntoView(option);
    await sleep(0.8);
  });
}

export function getFist3Version(versionStr: string) {
  return versionStr
    .split(".")
    .filter((i, index) => index < 3)
    .join(".");
}

export function extractBodyContent(html: string) {
  if (!html.startsWith("<html")) {
    return html;
  }

  const tempDiv = document.createElement("div");
  tempDiv.innerHTML = html;
  const styleElement = tempDiv.querySelector("style");
  styleElement?.remove();
  const bodyContent = tempDiv?.innerHTML;
  return bodyContent?.trim() || "";
}

export const formatRichTextHtml = (html: string) => {
  html = extractBodyContent(html);
  return html;
};

/**
 * 数组分段
 * @param arr
 * @param size 每段的长度
 * @returns
 */
export function breakArray<T>(arr: T[], size = 300) {
  const result: T[][] = [];
  const length = arr.length;

  let index = 0;
  while (index * size < length) {
    result.push(arr.slice(size * index, size * (index + 1)));
    index++;
  }
  return result;
}

export function removeIllegalCharacters(str) {
  if (!str) {
    return "-";
  }
  // 使用正则表达式匹配非法字符并替换为空字符串
  const illegalCharsRegex = /[\\/:"*?<>|]/g;
  return str.replace(illegalCharsRegex, "");
}
// 文字脱敏
export function desensitize(str: string) {
  if (str && typeof str === "string") {
    if (str.length >= 4) return "****";
    return "*".repeat(str.length);
  }
  return "";
}
export function xmlObjForEach(obj: any, cb: (parentValue: any, key: string, value: any) => void | "skipChild") {
  Object.entries(obj).forEach(entry => {
    const res = cb(obj, entry[0], entry[1]);
    if (res === "skipChild") {
      return;
    }
    if (typeof entry[1] === "object") {
      xmlObjForEach(entry[1], cb);
    }
  });
}

export function getFileSuffix(fileName: string) {
  const index = fileName.lastIndexOf(".");
  if (index === -1) return "";
  const suffix = fileName.substring(index + 1).toLocaleLowerCase();
  return suffix;
}

// 判断是否是乱码
export function isGarbledCharacter(c: string) {
  const unicode = c.charCodeAt(0);
  return (unicode < 0x20 || unicode > 0x7e) && unicode !== 0x0a && unicode !== 0x0d && unicode !== 0x09;
}
/**
 * 根据行索引和列索引返回Excel样式的单元格位置。
 *
 * @param {number} rowIndex - 行索引（从1开始计数）。
 * @param {number} colIndex - 列索引（从1开始计数）。
 * @returns {string} 对应的Excel单元格位置，如"A1", "B2", "AA10"等。
 */
function colNumToAlpha(col: number) {
  let alpha = "";
  while (col > 0) {
    let remainder = col % 26;
    alpha = String.fromCharCode(65 + (remainder - 1)) + alpha; // 注意调整为减1，因为余数是从0开始的
    col = Math.floor((col - 1) / 26); // 同样，这里减1以正确计算下一级的进位
  }
  return alpha || "A"; // 如果是第一列，返回'A'
}
/**
 * 根据坐标获取对应 Excel 位置
 * @param rowIndex
 * @param colIndex
 * @returns
 */
export function getExcelPosition(rowIndex: number, colIndex: number) {
  let colAlpha = colNumToAlpha(colIndex);
  return colAlpha + rowIndex;
}
/**
 * 将对象转为FormData，注意对象属性的值必须是 string|Blob|number|boolean，number 和boolean会被转化为string
 * value为null或者undefined的属性会被忽略
 * @param obj
 * @returns FormData
 */
export function objectToFormData(obj: Record<string, string | Blob | number | boolean>) {
  const formData = new FormData();
  for (const key in obj) {
    if (!isEmpty(obj[key])) {
      formData.append(key, obj[key] as string);
    }
  }

  return formData;
}

/**
 * 传入数据，随机返回一个
 */
export function getRandomItem<T>(items: T[]): T {
  const randomIndex = Math.floor(lodash.random(0, 1, true) * items.length);
  return items[randomIndex];
}
/**
 * 防抖Promise
 * @param fn
 * @param delay
 * @returns
 */
export function debouncePromise<T = any>(fn: (...arg0: any[]) => Promise<T>, delay = 300) {
  let pendingPromise: ReturnType<typeof setTimeout>;

  return function (this, ...args: any[]) {
    if (pendingPromise) {
      clearTimeout(pendingPromise);
    }
    const context = this;
    return new Promise((resolve, reject) => {
      pendingPromise = setTimeout(() => {
        fn.apply(context, args).then(resolve).catch(reject);
      }, delay);
    });
  };
}

export function isUpperCase(char) {
  return /^[A-Z]$/.test(char);
}

/**
 * 科学计数法+精度
 * @param number 初始数值
 * @param config.scientificNotation boolean 是否可是科学计数法
 * @param config.precisionLimit: boolean 是否开启精确度
 * @param config.precision: number 精确位，小数点后几位
 * @returns
 */
export function formatScientificNumber(number, config) {
  const { scientificNotation = false, precisionLimit = false, precision = null } = config || {};

  const numStr = number.toString().trim();
  if (!scientificNotation && !precisionLimit) return numStr;

  const num = parseFloat(numStr);
  if (num === 0) {
    return scientificNotation ? "0E0" : "0";
  }
  if (isNaN(num)) {
    return "";
  }

  // 校验 precision 范围（0~11），超出范围视为无效
  const validPrecision = precision !== null && Number.isInteger(precision) && precision >= 0 && precision <= 11 ? precision : null;

  // 核心处理逻辑
  if (scientificNotation) {
    if (precisionLimit && validPrecision !== null) {
      return formatWithPrecision(numStr, validPrecision);
    } else {
      return formatPureScientific(numStr);
    }
  } else {
    return formatDecimal(numStr, validPrecision);
  }
}

// 工具函数：带精度的科学计数法格式化
function formatWithPrecision(numStr, precision) {
  if (precision < 0 || precision > 11) {
    return formatPureScientific(numStr);
  }

  const [mantissa, exponent] = parseScientificComponents(numStr);
  const rounded = roundMantissa(mantissa, precision);
  return `${rounded}E${exponent}`;
}

// 工具函数：纯科学计数法（保留原始精度）
function formatPureScientific(numStr) {
  const [mantissa, exponent] = parseScientificComponents(numStr);
  return `${mantissa}E${exponent}`;
}

// 核心解析器：从字符串提取尾数和指数
function parseScientificComponents(numStr) {
  // 标准化输入格式（处理科学计数法字符串）
  let [base, exp] = numStr.toLowerCase().split("e");
  exp = exp ? parseInt(exp, 10) : 0;

  // 处理基础部分
  let [intPart, decPart = ""] = base.split(".");
  intPart = intPart.replace(/^0+/, "") || "0";
  decPart = decPart.replace(/0+$/, "");

  // 合并所有有效数字
  const fullDigits = intPart + decPart;
  const firstSigIndex = fullDigits.search(/[1-9]/);

  // 计算实际指数
  let exponent;
  if (intPart === "0") {
    const decimalOffset = decPart.search(/[1-9]/);
    exponent = exp - (decimalOffset + 1);
  } else {
    exponent = exp + intPart.length - 1;
  }

  // 构建标准化尾数
  const sigDigits = fullDigits.slice(firstSigIndex).replace(/^0+/, "");
  const mantissa = sigDigits[0] + (sigDigits.length > 1 ? `.${sigDigits.slice(1)}` : "");

  return [mantissa, exponent];
}

// 尾数精度处理（字符串操作实现四舍五入）
function roundMantissa(mantissaStr, precision) {
  const [intPart, decPart = ""] = mantissaStr.split(".");
  const allDigits = intPart + (decPart || "");
  const targetLength = precision + 1; // 整数位 + 小数位

  // 补足位数
  let padded = allDigits.padEnd(targetLength, "0");
  if (padded.length > targetLength) {
    // 执行四舍五入
    const roundDigit = parseInt(padded[targetLength], 10);
    padded = padded.slice(0, targetLength);
    if (roundDigit >= 5) {
      const incremented = (BigInt(padded) + 1n).toString();
      padded = incremented.padStart(targetLength, "0");
    }
  }

  return padded[0] + (precision > 0 ? `.${padded.slice(1, precision + 1)}` : "");
}

// 普通数字的精度处理
function formatDecimal(numStr, precision) {
  if (precision === null) return numStr;

  const num = parseFloat(numStr);

  if (num % 1 === 0) {
    return num.toFixed(0);
  }

  return num.toFixed(precision);
}

export function parseArrayBufferToJSON(buffer: ArrayBuffer) {
  const str = Buffer.from(buffer).toString("utf-8");
  const data = JSON.parse(str);
  return data;
}

/**
 * 从树结构中递归查找并删除指定 id 的节点
 * @param tree 树结构数组
 * @param id 要删除的节点 id
 * @param idKey id 字段名，默认为 'id'
 * @param childrenKey 子节点字段名，默认为 'children'
 */
export function removeNodeById(tree: any[], id: string | number, idKey = "id", childrenKey = "children"): void {
  for (let i = tree.length - 1; i >= 0; i--) {
    const node = tree[i];
    if (node[idKey] === id) {
      tree.splice(i, 1);
    } else if (node[childrenKey] && Array.isArray(node[childrenKey])) {
      removeNodeById(node[childrenKey], id, idKey, childrenKey);
    }
  }
}

/**
 * 拍平树结构为一维数组
 * @param tree 树结构数组
 * @param childrenKey 子节点字段名，默认为 'children'
 * @returns 拍平后的一维数组
 */
export function flattenTree(tree: any[], childrenKey = "children"): any[] {
  const result: any[] = [];
  function traverse(nodes: any[]) {
    for (const node of nodes) {
      result.push(node);
      if (node[childrenKey] && Array.isArray(node[childrenKey])) {
        traverse(node[childrenKey]);
      }
    }
  }
  traverse(tree);
  return result;
}
